<template>
	<div class="h-100% flex-col">
		<div class="p-10 flex m-between" v-show="edit">
			<div>
				<el-tooltip effect="dark" content="新建" placement="bottom">
					<my-button icon="circle-plus" @click="$attrs['onUpdate:modelValue']({})" />
				</el-tooltip>
				<el-tooltip effect="dark" content="居中" placement="bottom">
					<my-button icon="Rank" @click="centerContentFn" />
				</el-tooltip>
				<el-tooltip effect="dark" content="放大" placement="bottom">
					<my-button icon="zoom-in" @click="zoomFn(0.2)" />
				</el-tooltip>
				<el-tooltip effect="dark" content="缩小" placement="bottom">
					<my-button icon="zoom-out" @click="zoomFn(-0.2)" />
				</el-tooltip>
				<el-tooltip effect="dark" content="后退" placement="bottom">
					<my-button icon="Back" @click="undoFn" :disabled="!canUndo" />
				</el-tooltip>
				<el-tooltip effect="dark" content="前进" placement="bottom">
					<my-button icon="Right" @click="redoFn" :disabled="!canRedo" />
				</el-tooltip>
			</div>

			<div>
				<my-button icon="View" @click="showJSON">查看json</my-button>
				<my-button icon="Download" @click="downloadJSON()">下载json</my-button>
				<my-button icon="Picture" @click="downloadImg()">下载svg</my-button>
			</div>
		</div>
		<div class="flex-auto flex">
			<div class="left p-10 flex-col items-center" ref="dndContainer" v-show="edit">
				<div v-for="item in nodeList" :class="item.type" class="item" @mousedown="startDrag(item, $event)">
					<el-image :src="getImgUrl(`x6/${item.type}.png`)"></el-image>
				</div>
			</div>
			<div class="f1 w0" ref="x6Container" />
			<div class="right p-10" v-show="edit">
				<my-form
					v-show="!isEmpty(formData)"
					label-position="top"
					:model="formData"
					:formItem="[
						{ prop: 'fillColor', label: '背景颜色', col: { span: 12 } },
						{ prop: 'strokeColor', label: '边框颜色', col: { span: 12 } },
						{ prop: 'fontSize', label: '字体大小', itemType: 'number', col: { span: 16 } },
						{ prop: 'fontColor', label: '字体颜色', col: { span: 6 } },
						{
							prop: 'fontWeight',
							label: '字体粗细',
							itemType: 'select',
							list: [
								{ label: 100, value: 100 },
								{ label: 200, value: 200 },
								{ label: 300, value: 300 },
								{ label: 400, value: 400 },
								{ label: 500, value: 500 },
								{ label: 600, value: 600 },
								{ label: 700, value: 700 },
								{ label: 800, value: 800 },
								{ label: 900, value: 900 }
							],
							fileType: { label: 'label', value: 'value' },
							col: { span: 12 }
						},
						{
							prop: 'letterSpacing',
							label: '字体间距',
							itemType: 'number',
							col: { span: 12 }
						},
						{
							prop: 'justifyContent',
							label: '对齐方式',
							itemType: 'select',
							list: [
								{ label: '左对齐', value: 'start' },
								{ label: '居中', value: 'center' },
								{ label: '右对齐', value: 'end' }
							],
							fileType: { label: 'label', value: 'value' }
						},
						{ prop: 'label', label: '内容', type: 'textarea', autosize: { minRows: 4 } }
					]"
				>
					<template #fillColor="{ model, prop }">
						<el-color-picker v-model="model[prop]" />
					</template>
					<template #strokeColor="{ model, prop }">
						<el-color-picker v-model="model[prop]" />
					</template>
					<template #fontColor="{ model, prop }">
						<el-color-picker v-model="model[prop]" />
					</template>
				</my-form>
			</div>
		</div>

		<!--在线查看json-->
		<el-dialog title="查看" v-model="jsonOpen" width="600" append-to-body draggable>
			<JsonEditor class="w-100%" style="height: 400px" v-model="JsonData" language="zh-CN"></JsonEditor>
		</el-dialog>
	</div>
</template>

<script setup>
import JsonEditor from 'my-json-editor';
import { isEmpty } from 'lodash';
import { downloadFile } from '@u/download';
import date from '@u/date';

/**
 * X6
 */
import { Graph, Shape } from '@antv/x6';
import { register, getTeleport } from '@antv/x6-vue-shape'; // 注册自定义节点
import { Dnd } from '@antv/x6-plugin-dnd';
import { Snapline } from '@antv/x6-plugin-snapline';
import { History } from '@antv/x6-plugin-history';
import { Transform } from '@antv/x6-plugin-transform';
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { Clipboard } from '@antv/x6-plugin-clipboard';
import { Selection } from '@antv/x6-plugin-selection';
import { Export } from '@antv/x6-plugin-export';

let x6Container = shallowRef(null),
	dndContainer = shallowRef(null),
	canUndo = ref(false),
	canRedo = ref(false),
	graph = {},
	dnd = {};

const props = defineProps({
		modelValue: {
			default: () => {}
		},
		title: {},
		edit: {
			type: Boolean,
			default: false
		}
	}),
	nodeList = [
		{
			type: 'r1',
			node: () => {
				return graph.createNode({
					shape: 'custom-rect',
					attrs: {
						body: {
							rx: 20,
							ry: 26
						}
					}
				});
			}
		},
		{
			type: 'r2',
			node: () => {
				return graph.createNode({
					shape: 'custom-rect'
				});
			}
		},
		{
			type: 'r3',
			node: () => {
				return graph.createNode({
					shape: 'custom-rect',
					attrs: {
						body: {
							rx: 6,
							ry: 6
						}
					}
				});
			}
		},
		{
			type: 'r4',
			node: () => {
				return graph.createNode({
					shape: 'custom-polygon',
					attrs: {
						body: {
							refPoints: '0,10 10,0 20,10 10,20'
						}
					}
				});
			}
		},
		{
			type: 'r5',
			node: () => {
				return graph.createNode({
					shape: 'custom-polygon',
					attrs: {
						body: {
							refPoints: '10,0 40,0 30,20 0,20'
						}
					}
				});
			}
		},
		{
			type: 'r6',
			node: () => {
				return graph.createNode({
					shape: 'custom-circle'
				});
			}
		}
	];

watch(
	() => props.edit,
	(val) => {
		graphDisabled();
		graph.centerContent();
	}
);

// 连接点
const ports = {
	groups: {
		top: {
			position: 'top',
			attrs: {
				circle: {
					r: 4,
					magnet: true,
					stroke: '#108ee9',
					strokeWidth: 1,
					fill: '#fff',
					style: {
						visibility: 'hidden'
					}
				}
			}
		},
		right: {
			position: 'right',
			attrs: {
				circle: {
					r: 4,
					magnet: true,
					stroke: '#108ee9',
					strokeWidth: 1,
					fill: '#fff',
					style: {
						visibility: 'hidden'
					}
				}
			}
		},
		bottom: {
			position: 'bottom',
			attrs: {
				circle: {
					r: 4,
					magnet: true,
					stroke: '#108ee9',
					strokeWidth: 1,
					fill: '#fff',
					style: {
						visibility: 'hidden'
					}
				}
			}
		},
		left: {
			position: 'left',
			attrs: {
				circle: {
					r: 4,
					magnet: true,
					stroke: '#108ee9',
					strokeWidth: 1,
					fill: '#fff',
					style: {
						visibility: 'hidden'
					}
				}
			}
		}
	},
	items: [
		{
			group: 'top'
		},
		{
			group: 'right'
		},
		{
			group: 'bottom'
		},
		{
			group: 'left'
		}
	]
};

onMounted(() => {
	graph = new Graph({
		container: x6Container.value,
		autoResize: true,
		panning: false,
		mousewheel: false,
		history: true,
		background: {
			color: '#f7f7f7'
		},
		connecting: {
			router: 'manhattan',
			connector: {
				name: 'rounded',
				args: {
					radius: 8
				}
			},
			anchor: 'center',
			connectionPoint: 'anchor',
			allowBlank: false,
			snap: {
				radius: 20
			},
			createEdge() {
				return new Shape.Edge({
					attrs: {
						line: {
							stroke: '#A2B1C3',
							strokeWidth: 2,
							targetMarker: {
								name: 'block',
								width: 12,
								height: 8
							}
						}
					},
					zIndex: 0
				});
			},
			validateConnection({ targetMagnet }) {
				return !!targetMagnet;
			}
		},
		highlighting: {
			magnetAdsorbed: {
				name: 'stroke',
				args: {
					attrs: {
						fill: '#5F95FF',
						stroke: '#5F95FF'
					}
				}
			}
		}
	});

	graph
		.use(new Transform({ resizing: { enabled: true } }))
		.use(new Selection({ showNodeSelectionBox: true }))
		.use(new Snapline())
		.use(new Keyboard())
		.use(new Clipboard())
		.use(new History())
		.use(new Export());
	graphDisabled();
	Graph.registerNode(
		'custom-rect',
		{
			inherit: 'text-block',
			width: 120,
			height: 60,
			attrs: {
				body: {
					strokeWidth: 1,
					stroke: '#000000',
					fill: '#fff'
				},
				label: {
					style: {
						color: '#262626',
						'white-space': 'pre-line'
					}
				}
			},
			ports: { ...ports }
		},
		true
	);

	Graph.registerNode(
		'custom-polygon',
		{
			inherit: 'text-block',
			width: 120,
			height: 60,
			markup: [
				{ tagName: 'polygon', selector: 'body' },
				{
					tagName: 'foreignObject',
					selector: 'foreignObject',
					children: [
						{
							tagName: 'div',
							ns: 'http://www.w3.org/1999/xhtml',
							selector: 'label',
							style: {
								width: '100%',
								height: '100%',
								position: 'static',
								backgroundColor: 'transparent',
								textAlign: 'center',
								margin: 0,
								padding: '0px 5px',
								boxSizing: 'border-box',
								display: 'flex',
								alignItems: 'center',
								justifyContent: 'center'
							}
						}
					]
				}
			],
			attrs: {
				body: {
					strokeWidth: 1,
					stroke: '#000000',
					fill: '#fff'
				},
				label: {
					style: {
						color: '#262626',
						'white-space': 'pre-line'
					}
				}
			},
			ports: { ...ports }
		},
		true
	);

	Graph.registerNode(
		'custom-circle',
		{
			inherit: 'text-block',
			width: 60,
			height: 60,
			markup: [
				{
					tagName: 'circle',
					selector: 'body'
				},
				{
					tagName: 'foreignObject',
					selector: 'foreignObject',
					children: [
						{
							tagName: 'div',
							ns: 'http://www.w3.org/1999/xhtml',
							selector: 'label',
							style: {
								width: '100%',
								height: '100%',
								position: 'static',
								backgroundColor: 'transparent',
								textAlign: 'center',
								margin: 0,
								padding: '0px 5px',
								boxSizing: 'border-box',
								display: 'flex',
								alignItems: 'center',
								justifyContent: 'center'
							}
						}
					]
				}
			],
			attrs: {
				body: {
					strokeWidth: 1,
					stroke: '#000000',
					fill: '#fff',
					refCx: '50%',
					refCy: '50%',
					refR: '50%'
				},
				label: {
					style: {
						color: '#262626',
						'white-space': 'pre-line'
					}
				}
			},
			ports: { ...ports }
		},
		true
	);

	dnd = new Dnd({
		target: graph,
		dndContainer: dndContainer.value
	});

	onFn();

	bindKey();

	graph.fromJSON(JSON.parse(props.modelValue || '{}'));
	graph.centerContent();
});

function graphDisabled() {
	if (graph) {
		graph.togglePanning(props.edit);
		graph.toggleMouseWheel(props.edit);
		graph.options.interacting = props.edit;
		for (const plugin of graph.installedPlugins) {
			if (plugin.name == 'transform') {
				plugin.options.resizing.enabled = props.edit;
			} else {
				if (plugin?.options?.enabled != undefined) plugin.options.enabled = props.edit;
			}
		}
	}
}

watch(
	() => props.modelValue,
	(val) => {
		graph.fromJSON(JSON.parse(val || '{}'));
	}
);

// 监听事件
let activeNode;
function onFn() {
	// 是否可以前进、后退
	graph.on('history:change', () => {
		if (!props.edit) return;
		canRedo.value = graph.canRedo();
		canUndo.value = graph.canUndo();
	});
	// 点击节点
	graph.on('node:click', ({ e, x, y, node, view }) => {
		if (!props.edit) return;
		activeNode = node;
		const attrs = activeNode.getAttrs();
		formData.value = {
			fillColor: attrs.body.fill,
			strokeColor: attrs.body.stroke,
			fontSize: `${attrs.label.style.fontSize}`.replace('px', ''),
			fontColor: attrs.label.style.color,
			fontWeight: attrs.label.style.fontWeight,
			letterSpacing: `${attrs.label.style.letterSpacing}`.replace('px', ''),
			justifyContent: attrs.label.style.justifyContent,
			label: attrs.label.text || ''
		};
	});
	// 画布空白区域
	graph.on('blank:click', ({ e, x, y, node, view }) => {
		if (!props.edit) return;
		activeNode = null;
		formData.value = {};
	});
	// 鼠标移入
	graph.on('node:mouseenter', ({ node }) => {
		if (!props.edit) return;
		showPorts(true);
	});
	// 鼠标移出
	graph.on('node:mouseleave', ({ node }) => {
		if (!props.edit) return;
		showPorts(false);
	});
}

// 快捷键
function bindKey() {
	// 复制粘贴
	graph.bindKey(['meta+c', 'ctrl+c'], () => {
		if (!props.edit) return;

		const cells = graph.getSelectedCells();
		if (cells.length) {
			graph.copy(cells);
		}
		return false;
	});
	graph.bindKey(['meta+x', 'ctrl+x'], () => {
		if (!props.edit) return;

		const cells = graph.getSelectedCells();
		if (cells.length) {
			graph.cut(cells);
		}
		return false;
	});
	graph.bindKey(['meta+v', 'ctrl+v'], () => {
		if (!props.edit) return;

		if (!graph.isClipboardEmpty()) {
			const cells = graph.paste({ offset: 32 });
			graph.cleanSelection();
			graph.select(cells);
		}
		return false;
	});
	// 删除
	graph.bindKey('backspace', () => {
		if (!props.edit) return;

		const cells = graph.getSelectedCells();
		if (cells.length) {
			graph.removeCells(cells);
		}
	});
	graph.bindKey('delete', () => {
		if (!props.edit) return;

		const cells = graph.getSelectedCells();
		if (cells.length) {
			graph.removeCells(cells);
		}
	});
	// 回滚 前进
	graph.bindKey(['meta+z', 'ctrl+z'], () => {
		if (!props.edit) return;

		if (graph.canUndo()) {
			graph.undo();
		}
		return false;
	});
	graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
		if (!props.edit) return;

		if (graph.canRedo()) {
			graph.redo();
		}
		return false;
	});
}

// 连接点的隐藏显示
function showPorts(show) {
	const ports = x6Container.value.querySelectorAll('.x6-port-body');
	for (let i = 0, len = ports.length; i < len; i += 1) {
		ports[i].style.visibility = show ? 'visible' : 'hidden';
	}
}

// 新增node
function startDrag(data, e) {
	const node = nodeList.find((item) => item.type == data.type).node();
	dnd.start(node, e);
}

// 放大、缩小、回滚、居中
function centerContentFn() {
	if (!props.edit) return;

	graph.centerContent();
}
function zoomFn(val) {
	if (!props.edit) return;

	graph.zoom(val);
}
function undoFn() {
	if (!props.edit) return;

	graph.undo();
}
function redoFn() {
	if (!props.edit) return;

	graph.redo();
}

// 设置节点内容
const formData = ref({}),
	attrs = useAttrs();

watch(
	formData,
	(val) => {
		if (activeNode) {
			activeNode.setAttrs({
				body: { fill: val.fillColor, stroke: val.strokeColor },
				label: {
					text: val.label,
					style: {
						fontSize: val.fontSize + 'px',
						color: val.fontColor,
						justifyContent: val.justifyContent,
						letterSpacing: val.letterSpacing + 'px',
						fontWeight: val.fontWeight
					}
				}
			});
		}
	},
	{
		deep: true
	}
);

/**
 * 查看json
 */
const jsonOpen = ref(false),
	JsonData = ref({});
function showJSON() {
	JsonData.value = graph.toJSON();
	jsonOpen.value = true;
}
/**
 * 下载json
 */
function downloadJSON() {
	downloadFile(`${props.title || date(new Date())}.x6.json`, graph.toJSON(), 'application/json');
}

/**
 * 下载svg
 */
function downloadImg() {
	graph.exportPNG(`${props.title || date(new Date())}`);
}
/**
 * 保存为json
 */
function save() {
	attrs['onUpdate:modelValue'](JSON.stringify(graph.toJSON()));
}

defineExpose({
	save
});
</script>

<style lang="scss" scoped>
.left {
	width: 80px;

	.item {
		:deep() {
			.el-image {
				display: block;
			}
		}

		+ .item {
			margin-top: 10px;
		}
	}

	.r6 {
		width: 70%;
	}
}

.right {
	width: 250px;
}

:deep() {
	.x6-widget-transform {
		border-color: transparent;
	}

	.x6-widget-selection-box {
		border: 2px dashed #239edd;
	}

	.x6-node {
		word-break: break-all; /* 允许在非空格处强制换行 */
		word-wrap: break-word; /* 允许长单词或URL地址自动换行 */
	}
}
</style>
